<?php

///////////////////////////////////////////////////////////////////////////////
/**
 * This file contains the static TypeValidator class and the global function
 * is_date().
 *
 * System requirements:
 * <ul>
 * <li>PHP 5</li>
 * </ul>
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * The Connector library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * {@link http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License}
 * for more details.
 *
 * @author Per Egil Roksvaag
 * @copyright 2009 Per Egil Roksvaag
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @package connector
 * @version 2.0.0
 */

///////////////////////////////////////////////////////////////////////////////
/**
 * Use the static TypeValidator class to validate an associated array of
 * parameters against a type definition map.
 * @package connector
 */

class TypeValidator
{
	///////////////////////////////////////////////////////////////////////////
	/**
	 * @var array Valid type identifiers
	 */

	static public $isType = array("bool", "int", "float", "date", "string", "array", "object");

	///////////////////////////////////////////////////////////////////////////
	/**
	 * @var array Valid boolean values
	 */

	static public $isBool = array(0, 1, -1, "0", "1", "-1", "true", "TRUE", "false", "FALSE");

	///////////////////////////////////////////////////////////////////////////
	/**
	 * Validate an associated array of parameters against a type definition map.
	 *
	 * Normally, you don't need to call this function directly. Ist's called
	 * from every select(), insert(), update() and delete() function. See the
	 * {@link IConnector} interface.
	 *
	 * A <b>map</b> is an array of type definitions. A <b>type definition</b>
	 * is an array with two or three elements. Format:
	 *
	 * <pre>
	 * $map[] = array(name, type[, default]);
	 * name:    The key of an element in the associated array to validate.
	 * type:    A type identifier, see {@link TypeValidator::$isType}.
	 * default: An optional default value. Can be of any type, also null.
	 * </pre>
	 *
	 * If the type of <var>$param[name]</var> is wrong and can't be converted
	 * to the required type, an exception is thrown.
	 * If <var>$param[name]</var> doesn't exist or is null, the optional default
	 * value is used or - if no default value is given - an exception is thrown.
	 * Exsample:
	 *
	 * @param array $param An associated array of parameters to validate.
	 * @param array $map An array of type definitions.
	 * @throws Exception when $param doesn't match the type definition $map.
	 * @return array A valid (and possibly modified) associated array of parameters.
	 */

	static public function check($param, $map)
	{
		if(is_array($param) == false)
		{
			throw new Exception("TypeValidator - Wrong param format: the param must be an array.");
		}
		if(is_array($map) == false)
		{
			throw new Exception("TypeValidator - Wrong map format: the map must be an array.");
		}
		foreach($map as $field)
		{
			self::checkField($field);
			self::checkParam($param, $field);
		}
		return $param;
	}

	///////////////////////////////////////////////////////////////////////////
	/**
	 * Checks if a type definition is valid. If not, an exception is thrown.
	 *
	 * @param array $field The definition of a single field.
	 * @throws Exception when an invalid type definition is given.
	 * @return void
	 */

	static protected function checkField($field)
	{
		if(!is_array($field))
		{
			throw new Exception("TypeValidator - Wrong field format: the field must be an array.");
		}
		if(count($field) < 2)
		{
			throw new Exception("TypeValidator - Wrong field format: name and type must be definded.");
		}
		else
		{
			list($name, $type) = $field;
		}
		if(!is_int($name) && !is_string($name))
		{
			throw new Exception("TypeValidator - Wrong field format: name must be alphanumeric.");
		}
		if(!in_array($type, self::$isType))
		{
			throw new Exception("TypeValidator - Wrong field format: unknown type.");
		}
	}

	///////////////////////////////////////////////////////////////////////////
	/**
	 * Check if an associated array of parameters match a type definition.
	 * If not, an exception is thrown.
	 *
	 * Valid parameters may be are converted or
	 * substituted to match the field definitien.
	 *
	 * @param array &$param An associated array of parameters to validate.
	 * @param array $field A type definition array.
	 * @throws Exception when $param doesn't match the type definition.
	 * @return void
	 */

	static protected function checkParam(&$param, $field)
	{
		list($name, $type) = $field;

		if(array_key_exists($name, $param))
		{
			if(self::checkType($param[$name], $type))
			{
				self::cast($param[$name], $type);
			}
			else if(is_null($param[$name]) && count($field) > 2)
			{
				$param[$name] = $field[2];
			}
			else
			{
				$found = gettype($param[$name]);
				throw new Exception("TypeValidator - Wrong type in field {$name}, {$found} found.");
			}
		}
		else
		{
			if(count($field) > 2)
			{
				$param[$name] = $field[2];
			}
			else
			{
				throw new Exception("TypeValidator - Mandatory field {$name} is missing");
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////
	/**
	 * Check the type of a variable. A value of null allways returns false.
	 *
	 * @param mixed $value The value to validate.
	 * @param string $type A type identifier, see {@link TypeValidator::$isType}.
	 * @return bool true if the value has the correct type, false otherwise.
	 */

	static protected function checkType($value, $type)
	{
		if(is_null($value)) return false;

		switch($type)
		{
			case "bool":	return is_bool($value)	|| in_array($value, self::$isBool, true);
			case "int":		return is_int($value)	|| is_numeric($value);
			case "float":	return is_float($value)	|| is_numeric($value);
			case "date":	return is_date($value)	|| (strtotime($value) > 0);
			case "string":	return is_string($value);
			case "array":	return is_array($value);
			case "object":	return is_object($value);
		}
	}

	///////////////////////////////////////////////////////////////////////////
	/**
	 * Convert the type of a variable if necessary.
	 *
	 * @param mixed &$value The variable to cast.
	 * @param string $type A type identifier, see {@link TypeValidator::$isType}.
	 * @return void
	 */

	static protected function cast(&$value, $type)
	{
		if($type == "date" && !function_exists("date_create"))
		{
			return;
		}
		switch($type)
		{
			case "bool":	is_bool($value)		|| $value = ($value == true); break;
			case "int":		is_int($value)		|| $value = (int)$value; break;
			case "float":	is_float($value)	|| $value = (float)$value; break;
			case "date":	is_date($value)		|| $value = date_create($value); break;
			case "string":	is_string($value)	|| $value = (string)$value; break;
			case "array":	is_array($value)	|| $value = (array)$value; break;
			case "object":	is_object($value)	|| $value = (object)$value; break;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
/**
 * Shortcut type validation for DateTime.
 *
 * @param mixed $value The variable to validate.
 * @return bool true if $value is a DateTime variable, false otherwise.
 */

function is_date($value)
{
	return class_exists("DateTime", false) && ($value instanceof DateTime);
}

///////////////////////////////////////////////////////////////////////////////

?>